<template>
  <div class="upload-file-wrapper">
    <!-- 上传文件插件 -->
    <uploader
      class="uploader-app"
      :ref="uploaderRef"
      :options="options"
      :autoStart="false"
      :fileStatusText="fileStatusText"
      @file-added="handleFileAdded"
      @file-progress="handleFileProgress"
      @file-success="handleFileSuccess"
      @file-error="handleFileError">
      <uploader-btn
        class="select-file-btn"
        :ref="uploadBtnRef">选择指定文件</uploader-btn>
    </uploader>
    <transition name='fade'>
      <div class='uploader-loading-wrapper' v-if='isUploading && !isHideLoading'>
        <div class='loading-box'>
          <div class='box-title' v-if='loadingMode === "uploading"'>文件上传中...</div>
          <div class='box-title' v-if='loadingMode === "reading"'>文件读取中，请稍候...</div>
          <div class='box-slider'>
            <div class='slider-line' :style='{width: parseInt(process*100) + "%"}'></div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
/* eslint-disable */
// vue-simple-uploader文档及博客：
// https://blog.csdn.net/NineWaited/article/details/126594331
import SparkMD5 from 'spark-md5'
import { createFileGroup } from '@/api/file.js'

export default {
  name: 'shardUpoader',
  data () {
    return {
      uploaderInstance: {},
      uploaderRef: 'uploader_' + this.createUniqueId(),
      uploadBtnRef: 'uploaderBtn_' + this.createUniqueId(),
      options: {
        // singleFile: true,
        // target: `${process.env.VUE_APP_SERVICE_FILE_SERVICE}${process.env.VUE_APP_SERVICE_FILE}/fileTransfer/uploadFile`,
        target: `${process.env.VUE_APP_SERVICE_PORTAL}${process.env.VUE_APP_SERVICE_SYS}/app/uploadIcon`,
        headers: {
					Authorization: `Bearer-${sessionStorage.getItem('accessToken')}`
				},
        chunkSize: 2 * 1024 * 1024,
        maxChunkRetries: 5, //  并发上传数，默认 5
        testChunks: true, //  是否开启分片已存在于服务器的校验
        // 服务器分片校验函数，秒传及断点续传基础
        checkChunkUploadedByResponse: function (chunk, message) {
          let objMessage = JSON.parse(message)
          if (objMessage.code === 200) {
            let data = objMessage.data
            if (data.skipUpload) {
              // console.log('分片已存在于服务器中');
              // 分片已存在于服务器中
              return true
            }
            return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
          } else {
            // console.log(objMessage.message)
            return true
          }
        },
      },
      // 文件状态文案映射
      fileStatusText: {
        success: '上传成功',
        error: 'error',
        uploading: '上传中',
        paused: '暂停中',
        waiting: '等待中'
      },
      callback: null, // 由外部传入，用于外部直接调用文件上传的回调
      isHideLoading: false, // 是否隐藏loading
      timer: null,
      resetProcessTimer: null,
      isUploading: false,
      loadingMode: 'reading',
      process: 0
    }
  },
  props: {
    // 支持的上传文件大小上限, 不传则无限制，单位是B
    maxSize: {
      type: Number
    },
    // 支持的文件后缀名格式数组，不传则无限制，按小写形式传入
    validSuffixs: {
      type: Array
    },
    // 支持断点续传的次数上限
    maxResendCount: {
      type: Number,
      default: 3
    },
    // 是否显示上传成功提示
    showSuccMsg: {
      type: Boolean,
      default: true
    },
    // 是否显示上传失败提示
    showFailMsg: {
      type: Boolean,
      default: true
    },
    // 上传文件对应的路径：即所属的模块名
    filePath: {
      type: String,
      default: ''
    }
  },
  methods: {
    /**
     * 直接上传文件对象，跳过选择文件步骤，用于外部直接调用
     */
    uploadFile (fileObj, callback, isHideLoading = false) {
      // 如果上一次上传的loading还没有隐藏，取消隐藏
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (callback) {
        this.callback = callback
      }
      this.isHideLoading = isHideLoading
      this.uploaderInstance.addFile(fileObj)
    },
    // 开始上传文件, 在组件外部调用
    selectFile () {
      this.callback = null
      this.isHideLoading = false
      this.$refs[this.uploadBtnRef].$el.click()
    },
    // 文件上传过程进度的回调
    handleFileProgress (rootFile, file, chunk) {
      // console.log('=== progress ===: ', this.uploaderInstance.progress())
      this.process = this.uploaderInstance.progress()
    },
    // 添加一个文件的回调
    async handleFileAdded (file) {
      if (this.resetProcessTimer) {
        clearTimeout(this.resetProcessTimer)
        this.resetProcessTimer = null
      }
      // console.log('=== handleFileAdded ===: ', file)
      // 验证上传文件的格式（后缀名）
      if (this.validSuffixs) {
        let fileSuffix = ''
        let stepArr = file.name.split('.')
        let stepArrLength = stepArr.length
        if (stepArrLength < 2) {
          fileSuffix = ''
        } else {
          fileSuffix = stepArr[stepArrLength - 1].toLowerCase()
        }
        let isValid = this.validSuffixs.filter(item => item.toLowerCase() === fileSuffix)[0]
        if (!isValid) {
          return this.showWarning('不支持上传的文件格式')
        }
      }
      // 验证上传文件的大小
      if (this.maxSize && file.size > this.maxSize) {
        return this.showWarning('文件的大小超过限制')
      }
      this.isUploading = true
      // 在正式上传文件前，先确保文件对应的模块分组已经创建
      await this.addFilePath(file)
      this.process = 0
      // 开始上传文件
      this.computeMD5(file)
    },
    // 调用文件分组创建接口，先确保文件对应的模块分组已经创建
    addFilePath (file) {
      return new Promise(resolve => {
        let params = {
          fileName: (this.filePath || this.getFilePath()).slice(1), // 需要创建的文件夹名
          filePath: '/', // 上级目录
          fileTypeId: this.getFileTypeId(file),
          preventWarning: true
        }
        createFileGroup(params).then(res => {
          resolve()
        })
      })
    },
    /**
     * 计算md5，实现断点续传及秒传
     * @param {object} file 文件信息
     */
    computeMD5(file) {
      let fileReader = new FileReader()
      // console.log(fileReader,'读取文件fileReader');
      let blobSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice
      let currentChunk = 0
      const chunkSize = 1 * 1024 * 1024
      let chunks = Math.ceil(file.size / chunkSize)
      let spark = new SparkMD5.ArrayBuffer()
      // console.log(spark,'sparksparksparksparksparkspark');
      // 文件状态设为"计算MD5"
      this.loadingMode = 'reading'
      file.statusStr = '计算MD5'
      file.pause()
      loadNext()
      fileReader.onload = (e) => {
        // console.log(e.target.result,'------------result');
        spark.append(e.target.result)
        if (currentChunk < chunks) {
          currentChunk++
          loadNext()
          // 实时展示MD5的计算进度
          file.statusStr = `校验MD5 ${((currentChunk / chunks) * 100).toFixed(
            0
          )}%`
        } else {
          let md5 = spark.end()
          this.calculateFileMD5End(md5, file)
        }
      }
      fileReader.onerror = () => {
        this.hideLoading()
        this.$notify({
          title: '错误',
          message: `文件${file.name}读取出错，请检查该文件`,
          type: 'error',
          duration: 2000
        })
        file.cancel()
      }
      function loadNext() {
        let start = currentChunk * chunkSize
        let end = start + chunkSize >= file.size ? file.size : start + chunkSize
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },
    /**
     * 文件MD5计算结束
     * @param {string} md5 文件 MD5 值
     * @param {object} file 文件对象
     */
    calculateFileMD5End(md5, file) {
      // 将自定义参数直接加载uploader实例的opts上
      Object.assign(this.uploaderInstance.opts, {
        query: {
          // 添加文件所属模块对应的目录, 如果没有传入filePath，则根据页面路由进行获取
          filePath: this.filePath || this.getFilePath() || '/',
          // 添加文件所属的
          fileTypeId: this.getFileTypeId(file),
          // 上传者
          userName: sessionStorage.getItem('accountName') || null
        }
      })
      file.uniqueIdentifier = md5
      file.resume()
      // 移除自定义状态
      file.statusStr = ''
      this.loadingMode = 'uploading'
    },
    /**
     * 文件上传成功 回调函数
     * @param {object} rootFile 成功上传的文件所属的根 Uploader.File 对象，它应该包含或者等于成功上传文件
     * @param {object} file 当前成功的 Uploader.File 对象本身
     * @param {string} response 服务端响应内容，永远都是字符串
     */
    handleFileSuccess (rootFile, file, response) {
      // console.log('success rootFile', rootFile)
      // console.log('success file', file)
      // console.log('success response', response)
      this.hideLoading()
      if (response == '') {
				file.statusStr = '上传失败'
        if (this.showFailMsg) {
          this.showErrMsg('上传失败')
        }
				return
			}
			let result = JSON.parse(response)
      // console.log('success result', result)
			if (result.code === 200 && result.data) {
				file.statusStr = ''
        if (this.showSuccMsg) {
          this.showSucc('上传成功')
        }
        // 生成文件链接并上传的文件数据
        // let userFileId = result.data.userFileIds[0] || ''
        // let origin = process.env.NODE_ENV === 'development' ? 'https://test.saas.yuemia.com' : location.origin
        // let url = `${origin}${process.env.VUE_APP_SERVICE_FILE_SERVICE}${process.env.VUE_APP_SERVICE_FILE}/fileTransfer/preview?userFileId=${userFileId}`
        let url = result.data.absoluteFileUrl
        // 如果直接调用的上传方法，触发回调函数
        if (this.callback && typeof this.callback === 'function') {
          return this.callback({
            url: url,
            name: file.name,
            size: file.size
          })
        // 如果使用组件选择的文件，触发成功事件
        } else {
          return this.$emit('success', url, file.name, file.size)
        }
			} else {
				file.statusStr = '上传失败'
        if (this.showFailMsg) {
          this.showErrMsg(result.message)
        }
			}
    },
    /**
     * 文件上传失败 回调函数
     * @param {object} rootFile 成功上传的文件所属的根 Uploader.File 对象，它应该包含或者等于成功上传文件
     * @param {object} file 当前成功的 Uploader.File 对象本身
     * @param {string} response 服务端响应内容，永远都是字符串
     */
    handleFileError(rootFile, file, response) {
      // console.log('rootFile2', rootFile)
      // console.log('file2', file)
      // console.log('response2', response)
      this.hideLoading()
      if (this.showFailMsg) {
        this.showErrMsg(response)
      }
    },
    // 隐藏loading
    hideLoading (timeout = 1000) {
      this.timer = setTimeout(() => {
        this.isUploading = false
        // 再关闭loading1秒后, 将进度调回0
        this.resetProcessTimer = setTimeout(() => {
          this.process = 0
          this.resetProcessTimer = null
        }, 1000)
      }, timeout)
    },
    // 所有上传的文件都属于运维平台
    getFilePath () {
      return '/运维平台'
    },
    // 根据文件后缀名，判断文件的类型(1:图片 2：文档 3：视频 4：音乐 5：其他)
    getFileTypeId (file) {
      // console.log('== getFileTypeId file ==: ', file)
      let fileSuffix = ''
      let stepArr = file.name.split('.')
      let stepArrLength = stepArr.length
      if (stepArrLength < 2) {
        fileSuffix = ''
      } else {
        fileSuffix = stepArr[stepArrLength - 1].toLowerCase()
      }
      // console.log('== fileSuffix ==: ', fileSuffix)
      let fileTypeId = 5 // 默认为其他类型
      let map = [
        { type: 1, name: '图片', suffixType: ['bmp','png','jpg','jpeg','gif','svg'] },
        { type: 2, name: '文档', suffixType: ['pdf','doc','docx','ppt','pptx','xls','xlsx','txt','rar','zip','jar'] },
        { type: 3, name: '视频', suffixType: ['mp4','avi','ogv','webm','mov'] },
        { type: 4, name: '音乐', suffixType: ['mp3','wma','wav','amr'] }
      ]
      map.forEach(item => {
        if (item.suffixType.includes(fileSuffix)) {
          fileTypeId = item.type
        }
      })
      return fileTypeId
    },
    init () {
      this.uploaderInstance = this.$refs[this.uploaderRef].uploader
    }
  },
  mounted () {
    this.init()
  }
}
/* eslint-disable */
</script>

<style lang='stylus' scoped>
.fade-enter, .fade-leave-to
  opacity 0
.fade-enter-active, .fade-leave-active
  transition all 0.3s ease
.upload-file-wrapper
  position fixed
  right 0
  bottom 0
  z-index 10000
  width 0
  height 0
  .uploader-loading-wrapper
    position fixed
    z-index 10000
    top 0
    bottom 0
    left 0
    right 0
    background rgba(0,0,0,0.1)
    .loading-box
      position absolute
      left 50%
      top 50%
      transform translate(-50%, -50%)
      width 300px
      height 140px
      background #fff
      border-radius 5px
      box-shadow 0 0 5px 0 rgba(0,0,0,0.1)
      display flex
      flex-direction column
      justify-content center
      align-items center
      padding 0 20px
      .box-title
        no-wrap()
        margin-bottom 20px
        padding 0 20px
        max-width 100%
        line-height 1.4
      .box-slider
        width 100%
        height 30px
        border 1px solid #ddd
        border-radius 4px
        .slider-line
          height 100%
          background #21cad6
          transition width 0.2s ease
</style>
